Skip to content

Add Droid SDK provider#2689

Open
0xSero wants to merge 14 commits into
pingdotgg:mainfrom
0xSero:ai/droid-sdk-provider
Open

Add Droid SDK provider#2689
0xSero wants to merge 14 commits into
pingdotgg:mainfrom
0xSero:ai/droid-sdk-provider

Conversation

@0xSero
Copy link
Copy Markdown

@0xSero 0xSero commented May 14, 2026

Intention

Add Droid as a first-class T3 Code provider using Factory's TypeScript SDK: https://github.com/Factory-AI/droid-sdk-typescript

This is still WIP while we validate more real Droid permission, file, MCP, auth, and long-running streaming flows.

What this adds

  • Adds droid to the shared provider, model, settings, runtime source, and driver contracts.
  • Registers a managed Droid provider driver plus inventory probe.
  • Wraps createSession / resumeSession from @factory/droid-sdk in a T3 provider adapter.
  • Supports Droid session start, resume, stop, interrupt, and in-session model / reasoning updates.
  • Streams assistant text, reasoning text, tool progress/results, token usage, MCP/auth status, title updates, turn completion, and runtime errors into canonical T3 runtime events.
  • Routes Droid permission callbacks through T3 approval requests.
  • Routes Droid ask-user callbacks through the existing structured user-input flow.
  • Sends supported image attachments (gif, jpeg, png, webp) as base64 SDK image sources resolved through the existing attachment store.
  • Discovers SDK-reported Droid models from initResult.availableModels, including user/custom models, while preserving custom model ids so duplicate underlying models remain selectable.
  • Adds Droid UI presence in Provider Settings and the model picker, including the provided icon.

Implementation shape

The Droid adapter has been split by responsibility instead of keeping one large file:

  • DroidAdapter.ts: session lifecycle and adapter orchestration.
  • provider/droid/DroidRuntimeEvents.ts: SDK message to T3 runtime event projection.
  • provider/droid/DroidSdkMappings.ts: SDK model, access, reasoning, usage, approval, and user-input mappings.
  • provider/droid/DroidAttachmentResolver.ts: attachment MIME validation and image loading.
  • provider/droid/DroidAdapterTypes.ts: shared Droid adapter types.

Security / safety notes

  • Droid remains disabled by default.
  • No SDK secrets, pairing tokens, or provider auth payloads are intentionally logged.
  • Permission requests are mediated through T3 approval events and default to cancellation on missing/stale sessions.
  • Attachment paths are resolved through the existing attachment store helper; unsupported MIME types fail before SDK submission.
  • The Droid binary path is configurable but defaults to droid; prompt content is sent through the SDK rather than shell interpolation.
  • Model discovery uses a short-lived SDK session in a temp cwd and closes it immediately after reading init metadata.

Validation

  • Real Droid CLI query: droid exec --model glm-5.1 --cwd /tmp ... returned droid-pong.
  • Live SDK model discovery returned 67 models, including 38 user/custom models.
  • Local T3 model picker showed custom Droid models such as HomeLab - GLM-5.1, HomeLab - Trinity-Large-Thinking, Direct - GPT-5.5-Fast-xHigh, and Direct - GPT-5.5-Low.
  • Review comments addressed: missing-session reads now fail; Droid rollback now honors numTurns; streaming, thinking, access-mode mapping, and custom model discovery were updated.
  • bun fmt passed.
  • bun lint passed with existing unrelated web warnings.
  • bun typecheck passed.
  • Focused Droid tests passed: cd apps/server && bun run test src/provider/Layers/DroidAdapter.test.ts src/provider/Layers/DroidProvider.test.ts.
  • Full bun run test previously passed; after rebasing, one full parallel run hit three unrelated web timeout failures, and rerunning those exact files in isolation passed.

Note

Add Droid SDK provider with session management, streaming turns, and UI integration

  • Adds a full DroidDriver and DroidAdapter implementing session start/resume, streamed turns with image attachments, permission/user-input prompting, turn interruption, and session lifecycle management.
  • Adds checkDroidProviderStatus to probe CLI availability and discover models dynamically; surfaces a pending/warning draft when the CLI is absent or timing out.
  • Introduces a medium-access runtime mode mapped to Droid's Medium autonomy level; the composer and access mode menu now render provider-specific mode options via getRuntimeModeOptions/getRuntimeModeConfig.
  • Adds DroidSettings to the settings schema, registers DroidDriver in the built-in driver list, and exposes the provider in the model picker sidebar with a 'new' badge.
  • Risk: rollback is currently unsupported and returns a validation error; text generation via this driver always fails with TextGenerationError.

Macroscope summarized 969aceb.


Note

Medium Risk
Adds a new first-class provider with process execution, streaming runtime-event projection, and approval/user-input routing; issues are mostly isolated but could impact session lifecycle and UI mode selection.

Overview
Introduces Droid as a new provider backed by @factory/droid-sdk, wiring it into server settings/contracts, provider registry, and UI provider/model pickers.

On the server, adds DroidDriver plus a DroidAdapter that manages session start/resume/stop/interrupt, maps SDK stream messages into canonical runtime events (content/tool/token-usage/auth/MCP/errors), resolves image attachments, and performs provider health/model discovery via the droid --version CLI and availableModels.

Adds a new RuntimeMode value, medium-access, exposed only for Droid in the UI and normalized away when switching to other providers; includes tests for the adapter/provider mappings and updates provider icons/metadata defaults.

Reviewed by Cursor Bugbot for commit 969aceb. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 450aae69-9461-482e-853b-675556598272

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 14, 2026
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts Outdated
@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels May 14, 2026
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts Outdated
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 15, 2026

Latest Droid follow-up fixes pushed in c72f19b:

  • Fixed streaming by reconciling SDK assistant_text_delta block IDs with final create_message content, so final messages fill missing text without duplicating streamed text.
  • Added final create_message fallback handling for assistant text and thinking blocks when deltas are absent.
  • Passed custom model IDs and reasoning effort into Droid spec mode via enterSpecMode({ specModeModelId, specModeReasoningEffort }) and updateSettings(...), which fixes thinking/spec mode for custom models.
  • Matched Droid access levels to SDK autonomy levels: Off, Low, Medium, High, while keeping Medium visible only for Droid UI.
  • Verified the live app model picker shows user/custom Droid models including Direct - GPT-5.5-Fast-xHigh, and the Droid access menu shows Off/Low/Medium/High.
  • Direct SDK smoke query with custom xHigh model returned droid-smoke-ok.

Validation: bun fmt, focused Droid adapter/provider tests, bun typecheck, bun lint, and full bun run test all pass.

Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 15, 2026

Addressed the unresolved Droid cleanup review thread in b1dabf8. stopSession now treats droid.close() as best-effort cleanup, matching the scoped finalizer behavior, so stopAll() can continue shutting down the remaining sessions even if one SDK close throws.

Added a regression test covering two resumed Droid sessions where one close() fails and verified both sessions are still removed.

Validation:

  • bun fmt
  • bun lint (passes with existing unrelated warnings)
  • bun typecheck
  • cd apps/server && bun run test src/provider/Layers/DroidAdapter.test.ts src/provider/Layers/DroidProvider.test.ts
  • bun run test full suite

Note: the first full-suite run hit a timeout in @t3tools/oxlint-plugin-t3code; that package passed in isolation, then the full suite passed on rerun.

@0xSero 0xSero changed the title WIP: Add Droid SDK provider Add Droid SDK provider May 15, 2026
@0xSero 0xSero marked this pull request as ready for review May 15, 2026 23:25
Comment thread apps/server/src/provider/Layers/DroidProvider.ts Outdated
Comment thread apps/web/src/components/chat/runtimeModePresentation.ts
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented May 15, 2026

Approvability

Verdict: Needs human review

1 blocking correctness issue found. This PR introduces a new provider integration (Droid SDK) with substantial new runtime logic, session management, and UI changes - a new feature requiring human review. Additionally, 3 unresolved review comments identify potential concurrency and logic bugs.

You can customize Macroscope's approvability policy. Learn more.

@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 15, 2026

Split the Droid adapter by responsibility in af7ffb0:

  • DroidAdapter.ts is now 433 lines and only owns session lifecycle / adapter orchestration.
  • provider/droid/DroidRuntimeEvents.ts handles Droid SDK message to runtime event projection.
  • provider/droid/DroidSdkMappings.ts handles SDK enum, model, access, user-input, and usage mappings.
  • provider/droid/DroidAttachmentResolver.ts handles attachment image validation/loading.
  • provider/droid/DroidAdapterTypes.ts holds shared adapter types.

The Droid implementation is now 989 lines total across focused modules instead of one nearly 900-line adapter file.

Validation:

  • bun fmt
  • bun lint (passes with existing unrelated web warnings)
  • bun typecheck
  • focused Droid tests: cd apps/server && bun run test src/provider/Layers/DroidAdapter.test.ts src/provider/Layers/DroidProvider.test.ts
  • full bun run test was rerun after rebase and hit three unrelated web timeout failures under parallel load; rerunning those exact three files in isolation passed: cd apps/web && bun run test src/environments/runtime/service.addSavedEnvironment.test.ts src/environments/runtime/service.threadSubscriptions.test.ts src/components/chat/MessagesTimeline.test.tsx

Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Addressed the two remaining Cursor Bugbot review threads in 8939e57:

  • Removed the redundant Droid pending-provider status ternary. The probe stays warning, while buildServerProvider correctly exposes top-level status: "disabled" whenever Droid settings are disabled. Added a regression test for the disabled Droid provider snapshot.
  • Normalized Droid-only medium-access when the selected provider is no longer Droid, falling back to auto-accept-edits and persisting that normalized draft value so the runtime-mode select cannot show a value outside its option list. Added runtime-mode presentation tests.

Validation:

  • bun fmt
  • bun lint (passes with existing unrelated web warnings)
  • bun typecheck
  • focused tests for DroidProvider and runtimeModePresentation
  • full bun run test passed: 125 files passed, 1 skipped; 1032 tests passed, 4 skipped.

@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Addressed the remaining token-usage review thread in 742ab64:

  • Droid now stores activeTokenUsage as T3 Code’s canonical ThreadTokenUsageSnapshot instead of the raw SDK TokenUsageUpdate.
  • thread.token-usage.updated and turn.completed now use the same canonical usage shape.
  • Extended DroidAdapter.test.ts to assert both event payloads match the expected canonical token accounting.

Validation after this fix:

  • bun fmt
  • bun lint (passes with existing unrelated web warnings)
  • bun typecheck
  • cd apps/server && bun run test src/provider/Layers/DroidAdapter.test.ts src/provider/Layers/DroidProvider.test.ts

Earlier in this pass, full bun run test also passed before the final token-usage-only patch: 125 files passed, 1 skipped; 1032 tests passed, 4 skipped.

Comment thread apps/server/src/provider/droid/DroidRuntimeEvents.ts
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Follow-up fixes for the latest automated review threads:

  • Prevented Droid thinking text from being emitted twice when streamed ThinkingTextDelta events are followed by a final CreateMessage thinking block.
  • Hardened Droid interrupt handling so SDK/IPC interrupt failures are ignored after aborting the active turn instead of surfacing as fiber defects.

Validation:

  • bun fmt
  • bun lint (passes with existing unrelated warnings)
  • bun typecheck
  • bun run test

Comment thread apps/server/src/provider/droid/DroidSdkMappings.ts
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Follow-up for the latest Cursor Bugbot token usage thread:

  • Droid token usage now preserves cumulative totals across turns while keeping last* fields scoped to the current turn.
  • Added a regression test that runs two Droid turns and verifies cumulative usedTokens/inputTokens/outputTokens plus per-turn last* values.

Validation after this change:

  • bun fmt
  • bun lint (passes with existing unrelated warnings)
  • bun typecheck
  • bun run test

Comment thread apps/server/src/provider/droid/DroidRuntimeEvents.ts Outdated
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Fixed the latest Cursor Bugbot thread:

  • CreateMessage now uses the same ${messageId}-${index} key as streamed Droid deltas, so final blocks with SDK id fields do not bypass deduplication.
  • Updated the assistant text and thinking dedup regression tests to include final content block ids.

Validation:

  • bun fmt
  • bun lint (passes with existing unrelated warnings)
  • bun typecheck
  • bun run test

Comment thread apps/server/src/provider/Layers/DroidAdapter.ts Outdated
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts Outdated
Comment thread apps/server/src/provider/droid/DroidRuntimeEvents.ts
Comment thread apps/server/src/provider/droid/DroidRuntimeEvents.ts
Comment thread apps/server/src/provider/Layers/DroidProvider.ts
Comment thread apps/web/src/components/ChatView.tsx Outdated
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Addressed the latest Droid review batch in 450fa08c.

  • Resumed Droid sessions now immediately apply the requested autonomy/access level plus current model/reasoning settings via updateSettings.
  • Starting Droid twice for the same T3 thread now closes the replaced SDK session before installing the new context.
  • User interrupts now complete aborted Droid streams as interrupted instead of surfacing provider failures.
  • Droid SDK error stream messages now make the active turn fail and preserve the session error state.
  • Rollback now fails as unsupported for Droid instead of mutating only T3 local snapshots while the SDK session keeps stale history.
  • Final assistant completion dedup uses the same message/block key as streamed and final text content.
  • Model discovery now passes the Effect cancellation signal through to createSession so timeout interruption can close the probe process.
  • Runtime-mode normalization in ChatView now waits for provider config before persisting fallback normalization, avoiding temporary Droid medium-access downgrades.

Validation: bun fmt, bun lint (existing 9 warnings only), bun typecheck, and full bun run test all pass.

Comment thread apps/web/src/components/ChatView.tsx
Comment thread apps/server/src/provider/Layers/DroidAdapter.ts
@0xSero
Copy link
Copy Markdown
Author

0xSero commented May 16, 2026

Resolved the latest Bugbot findings in 969aceb.

  • Made ChatView runtime-mode normalization instance-aware and gated persistence until the selected provider instance is actually present/stable, so Droid medium-access does not get downgraded during reconnect/provider reload fallbacks.
  • Added a Droid sendTurn active-turn guard to reject overlapping turns before shared mutable context is reset, plus a regression test for concurrent sends.
  • Updated the snapshot/rollback test to wait for turn completion between serialized sends.

Validation on this commit:

  • bun fmt
  • bun lint (existing warnings only, 0 errors)
  • bun typecheck
  • bun run test

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 969aceb. Configure here.

context.activeAbort = undefined;
}
}
}).pipe(Effect.forkDetach);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detached streaming fiber races with queue shutdown in finalizer

Medium Severity

The streaming turn runs in an Effect.forkDetached fiber, making it immune to scope interruption. When the finalizer aborts contexts and then immediately calls Queue.shutdown(runtimeEvents), the detached fiber may still be executing its catch/finally handler. If completeInterruptedTurn or completeFailedTurn calls emitNow after the queue is shut down, Queue.offer fails and runPromise produces an unhandled promise rejection, since there's no outer try-catch wrapping the awaited emitNow calls inside the catch block.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 969aceb. Configure here.

resolvedUnlockedProvider !== undefined);
const runtimeMode = canNormalizeRuntimeMode
? normalizeRuntimeModeForProvider(selectedProvider, rawRuntimeMode)
: rawRuntimeMode;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider resolution doesn't check enabled status for mode normalization

Medium Severity

resolveProviderDriverKindForInstanceSelection returns a driver kind even for disabled provider instances, unlike the old resolveSelectableProvider which verified requestedEntry?.enabled before returning. When a user has a disabled Droid instance selected, resolvedUnlockedProvider resolves to "droid", canNormalizeRuntimeMode becomes true, and medium-access mode is preserved even though the Droid provider cannot execute turns. This persists a provider-specific runtime mode that other enabled providers don't support.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 969aceb. Configure here.

Comment on lines +262 to +291
if (context.session.status === "running" || context.activeAbort) {
return yield* new ProviderAdapterValidationError({
provider: DROID_PROVIDER,
operation: "sendTurn",
issue: `Droid thread ${input.threadId} already has an active turn.`,
});
}
const text = input.input?.trim();
const images = yield* resolveDroidImages(input.attachments ?? [], {
attachmentsDir: serverConfig.attachmentsDir,
fileSystem,
});
if (!text && images.length === 0) {
return yield* new ProviderAdapterValidationError({
provider: DROID_PROVIDER,
operation: "sendTurn",
issue: "Droid turns require text input or at least one attachment.",
});
}

const turnId = TurnId.make(`droid-turn-${randomUUID()}`);
const abort = new AbortController();
context.activeAbort = abort;
context.activeAssistantItems = new Map();
context.activeThinkingItems = new Map();
context.activeCompletedAssistantItems = new Set();
context.activeTurnError = undefined;
context.activeTokenUsage = undefined;
context.activeTokenUsageBaseline = context.cumulativeTokenUsage;
context.turns.push({ id: turnId, items: [] });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Layers/DroidAdapter.ts:262

In sendTurn, the guard check at lines 262-268 validates context.session.status !== 'running' and !context.activeAbort, but yield* resolveDroidImages(...) at lines 270-273 can suspend the effect before activeAbort is assigned at line 284. If two concurrent calls enter for the same threadId, both pass the guard before either sets activeAbort, causing overlapping turns to corrupt shared state (activeAssistantItems, activeThinkingItems, activeTokenUsage). Consider using a synchronous atomic operation (like a compare-and-swap on activeAbort) to ensure only one caller proceeds.

-      if (context.session.status === "running" || context.activeAbort) {
+      if (context.activeAbort) {
         return yield* new ProviderAdapterValidationError({
           provider: DROID_PROVIDER,
           operation: "sendTurn",
-          issue: `Droid thread ${input.threadId} already has an active turn.`,
+          issue: `Droid thread ${input.threadId} is busy (active turn or abort in progress).`,
         });
       }
+      const abort = new AbortController();
+      context.activeAbort = abort;
       const text = input.input?.trim();
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/DroidAdapter.ts around lines 262-291:

In `sendTurn`, the guard check at lines 262-268 validates `context.session.status !== 'running'` and `!context.activeAbort`, but `yield* resolveDroidImages(...)` at lines 270-273 can suspend the effect before `activeAbort` is assigned at line 284. If two concurrent calls enter for the same `threadId`, both pass the guard before either sets `activeAbort`, causing overlapping turns to corrupt shared state (`activeAssistantItems`, `activeThinkingItems`, `activeTokenUsage`). Consider using a synchronous atomic operation (like a compare-and-swap on `activeAbort`) to ensure only one caller proceeds.

Evidence trail:
apps/server/src/provider/Layers/DroidAdapter.ts lines 253-296 (guard at 262, yield at 270, state mutation at 284-296); apps/server/src/provider/droid/DroidAttachmentResolver.ts lines 23-71 (resolveDroidImages performs file I/O via fileSystem.readFile at line 53, creating an async suspension point)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants